package com.zzyl.serve.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.common.utils.bean.BeanUtils;
import com.zzyl.serve.domain.AlertData;
import com.zzyl.serve.domain.DeviceData;
import com.zzyl.serve.mapper.DeviceMapper;
import com.zzyl.serve.mapper.NursingElderMapper;
import com.zzyl.serve.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.serve.mapper.AlertRuleMapper;
import com.zzyl.serve.domain.AlertRule;
import com.zzyl.serve.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

/**
 * 报警规则Service业务层处理
 * 
 * @author liuyp
 * @date 2025-08-26
 */
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService
{
    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 查询报警规则
     * 
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id)
    {
        return alertRuleMapper.selectById(id);
    }

    /**
     * 查询报警规则列表
     * 
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule)
    {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule)
    {
        return alertRuleMapper.insert(alertRule);
    }

    /**
     * 修改报警规则
     * 
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule)
    {
        return alertRuleMapper.updateById(alertRule);
    }

    /**
     * 批量删除报警规则
     * 
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids)
    {
        return alertRuleMapper.deleteBatchIds(Arrays.asList(ids));
    }

    /**
     * 删除报警规则信息
     * 
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id)
    {
        return alertRuleMapper.deleteById(id);
    }

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    /**
     * 实现的功能目标：获取设备上报的所有数据，遍历处理每条数据
     */
    @Override
    public void handleAlertJob() {
        //1. 前置校验：如果没有任何已启用的报警规则，就直接结束
        long count = count(Wrappers.<AlertRule>lambdaQuery().eq(AlertRule::getStatus, 1));
        if (count == 0) {
            return;
        }

        //2. 从Redis中获取设备上报的数据。取hash里所有的value值：hvals key
        List<Object> dataArrayList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (ObjectUtil.isEmpty(dataArrayList)) {
            return;
        }

        //3. 将所有设备上报的数据转换成List<DeviceData>，并处理每个设备数据
        for (Object dataArrayJson : dataArrayList) {
            List<DeviceData> list = JSONUtil.toList(dataArrayJson.toString(), DeviceData.class);
            for (DeviceData deviceData : list) {
                //将每一条设备数据 交给这个方法进行处理
                handleDeviceData(deviceData);
            }
        }
    }

    /**
     * 处理一条设备的数据，校验的是否需要报警
     * @param deviceData
     */
    private void handleDeviceData(DeviceData deviceData) {
        //1. 如果这条设备数据已经过去1分钟了，就直接结束（因为定时任务每分钟执行一次，一分钟之前的数据应该是已经被处理过的）
        LocalDateTime alarmTime = deviceData.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) {
            return;
        }

        //2. 查询能够针对此数据生效的报警规则
        LambdaQueryWrapper<AlertRule> wrapper = Wrappers.<AlertRule>lambdaQuery()
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .in(AlertRule::getIotId, "-1", deviceData.getIotId());
        List<AlertRule> alertRuleList = list(wrapper);
        if (ObjectUtil.isEmpty(alertRuleList)) {
            return;
        }

        //3. 遍历每个规则：判断当前这条设备 对 每条规则是否触发报警
        for (AlertRule alertRule : alertRuleList) {
            handleDeviceDataAndRule(deviceData, alertRule);
        }
    }


    @Autowired
    private SysUserRoleMapper userRoleMapper;
    @Autowired
    private NursingElderMapper nursingElderMapper;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 处理此设备与当前规则的对比判断
     * @param deviceData 要判断的设备数据
     * @param alertRule 要使用的报警规则
     */
    private void handleDeviceDataAndRule(DeviceData deviceData, AlertRule alertRule) {
        //1. 判断这条设备数据的上报时间，在不在这个规则的有效时间范围内
        //1.1 获取此规则的有效时间范围：08:00:00~20:59:59
        String period = alertRule.getAlertEffectivePeriod();
        //1.2 将字符串切分成数组：split("分隔符")
        String[] split = period.split("~");
        LocalTime start = LocalTime.parse(split[0]);
        LocalTime end = LocalTime.parse(split[1]);
        //1.3 如果设备的上报时间早于开始时间  或者  设备的上报时间晚于结束时间：都不需要处理
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
        if (alarmTime.isBefore(start) || alarmTime.isAfter(end)) {
            return;
        }

        //2. 如果此设备数据没有异常，直接结束
        //2.1 首先获取设备上报的数据
        double value = Double.parseDouble(deviceData.getDataValue());
        //2.2 获取规则里的 操作符(<或>=)和阈值：如果符号是<并且设备值不小于阈值，是没有异常； 如果是>=但设备值<阈值，是没有异常
        String countKey = StringUtils.format(CacheConstants.IOT_ALERT_COUNT_KEY, deviceData.getIotId(), alertRule.getId());
        if ( ("<".equals(alertRule.getOperator()) && value >= alertRule.getValue())
            || (">=".equals(alertRule.getOperator()) && value < alertRule.getValue()) ) {
            //没有异常，要清理掉异常次数
            redisTemplate.delete(countKey);
            return;
        }

        //3. 如果此设备数据没有达到报警阈值，直接结束
        //3.1 如果当前正处于报警的沉默期间(之前已经通过过责任人去处理了)，则直接结束
        //      如果最终报警了，通知了责任人，那么就要设置一个沉默的标志，有效期是“报警沉默周期”
        //      只要查询一下是否有沉默的标志，如果有就直接结束
        //    我们将沉默标记存储到Redis里，准备一个key是 设备标识+规则标识 ，存储值随便比如“1”
        String silentFlagKey = StringUtils.format(CacheConstants.IOT_ALERT_SILENT_KEY, deviceData.getIotId(), alertRule.getId());
        Boolean isSilent = redisTemplate.hasKey(silentFlagKey);
        if (isSilent) {
            return;
        }

        //3.2 如果异常持续周期 没有达到上限，同样不需要报警，直接结束
        //  每次定时任务执行时，发现数据异常，要统计异常次数+1（持续周期+1）。持续周期（导演次数）同样存储到Redis里
        //  Redis的key：包含设备id + 规则id， value值：异常的次数。 Redis的string命令，数字值自增1：incr key。如果key不存在，则自动从0开始自增
        Long count = redisTemplate.opsForValue().increment(countKey, 1);
        //  如果本次异常次数+1以后，仍然没有达到持续周期的报警要求，就直接结束
        if (count < alertRule.getDuration()) {
            return;
        }

        //4. 确定需要发出报警了
        //4.1 清理掉异常持续周期（异常次数）
        redisTemplate.delete(countKey);
        //4.2 进入沉默期间：添加一个沉默标志
        redisTemplate.opsForValue().set(silentFlagKey, "1", alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        //4.3 查找此次报警的责任人
        Set<Long> userIds = new HashSet<>();
        if (alertRule.getAlertDataType() == 1) {
            //是设备异常，要根据角色“维修工”找到关联的用户id集合，是责任人
            Set<Long> ids = userRoleMapper.selectUserIdsByRoleName("维修工");
            userIds.addAll(ids);
        } else if (alertRule.getAlertDataType() == 0) {
            //是老人异常，最终要找到老人的护理员的id集合
            if (deviceData.getLocationType() == 0) {
                //是随身设备，设备绑定的位置是老人的id，可以根据老人id找到他的护理员id
                Set<Long> ids = nursingElderMapper.selectNurseIdsByElderId(deviceData.getAccessLocation());
                userIds.addAll(ids);
            }else if (deviceData.getLocationType() == 1 && deviceData.getPhysicalLocationType()==2){
                //是固定设备，设备绑定的是床位，要通过床位找到老人id，再根据老人id找到他的护理员id
                Set<Long> ids = deviceMapper.selectNurseIdsByBedIotId(deviceData.getIotId());
                userIds.addAll(ids);
            }
        }
        //4.4 将超级管理员也添加为责任人，也要能收到通知
        Set<Long> admins = userRoleMapper.selectUserIdsByRoleName("超级管理员");
        userIds.addAll(admins);

        //4.5 保存报警数据
        String reason = deviceData.getFunctionId()+alertRule.getOperator()+alertRule.getValue()+"持续" + alertRule.getDuration()+"周期";
        List<AlertData> list = new ArrayList<>();
        for (Long userId : userIds) {
            AlertData alertData = new AlertData();
            //将设备数据拷贝给alertData
            BeanUtils.copyProperties(deviceData, alertData);
            alertData.setId(null);

            //补充数据
            alertData.setAlertRuleId(alertRule.getId());
            alertData.setAlertReason(reason);
            alertData.setType(alertRule.getAlertDataType());
            alertData.setStatus(0);
            alertData.setUserId(userId);

            //添加到集合里
            list.add(alertData);
        }

        alertDataService.saveBatch(list);
    }
}
